package com.web_bloks.storage.client;

import com.google.gwt.gears.client.Factory;
import com.google.gwt.gears.client.database.Database;
import com.google.gwt.gears.client.database.DatabaseException;
import com.google.gwt.gears.client.database.ResultSet;

import com.web_bloks.system.client.Logger;

/**
 * 
 * @author Jean-Lou Dupont
 *
 */
public class GearsSimpleStorage 
	implements SimpleObjectStorage {

	public final static int DEFAULT_TTL = 24*60*60*1000;
	public final static long DEFAULT_CACHE_TIMEOUT = 24*60*60*1000; // 1day in milli
	
	/**
	 * Follows SQLite datatypes
	 * 
	 * class:    class-name             TEXT
	 * name:  	 Unique Name(within ns)	TEXT
	 * nsname:	 Unique namespace		TEXT
	 * content:	 content data			BLOB
	 * meta:     meta data				TEXT
	 * expires:  expiration             TEXT
	 * modified: last modification      TEXT
	 * created:  creation               TEXT
	 */
	final static String thisSchema = "(class TEXT, nsname TEXT, name TEXT, content BLOB, meta TEXT, expires TEXT, modified TEXT, created TEXT)";
	
	/**
	 * Storage name
	 *  i.e. database name
	 */
	String storageName = null;
	
	/**
	 * Database instance
	 */
	Database db = null;
	
	/*===================================================================
	 * CONSTRUCTORS 
	 ===================================================================*/
	public GearsSimpleStorage() {
		super();
	}
	/*===================================================================
	 * CONFIGURATION  
	 ===================================================================*/
	public void setStorageName(String sname) {
		this.storageName = new String( sname );
	}
	public String getStorageName() {
		return this.storageName;
	}

	/*===================================================================
	 * API  
	 ===================================================================*/
	public void put(SimpleObject obj) throws SimpleObjectStorageException {
		
		String classe  = obj.getClass().toString();
		
		String nsname  = obj.getSimpleObjectNamespace();
		String name    = obj.getSimpleObjectName();
		
		String meta    = obj.getSimpleObjectMeta();
		
		String created = obj.getSimpleObjectCreated();
		String modified= obj.getSimpleObjectModified();
		String expires = obj.getSimpleObjectExpires();
		
		String content = null;

		if ( null == name || name.length()==0 ) {
			throw new SimpleObjectStorageException( this.getClass() + ".put, storage:["+storageName+"]"+" name cannot be null or empty" );
		}
		if ( null == nsname || nsname.length()==0 ) {
			throw new SimpleObjectStorageException( this.getClass() + ".put, storage:["+storageName+"]"+" nsname cannot be null or empty" );
		}

		
		content = obj.serialize();
		
		ResultSet result = null;
		int lastrow = 0;
		
		try {
			result = this.db.execute("INSERT OR REPLACE INTO simpleobjectstore (class, nsame, name, meta, content, expires, modified, created) VALUES(?,?,?,?,?,?,?)", 
							new String[] {classe, nsname, name, meta, content, expires,modified, created } );
			lastrow = this.db.getLastInsertRowId();
			
			Logger.logDebug(this.getClass()+".put: result count: " + result.getFieldCount() +", lastrowid:" + lastrow);
			
		} catch(DatabaseException e) {
			Logger.logError(this.getClass()+".put, storage:["+storageName+"]"+".put: EXCEPTION raised");
			throw new SimpleObjectStorageException( e.getMessage() );
		}
		
		Logger.logDebug(this.getClass()+".put, storage:["+storageName+"]"+".put: stored nsname["+nsname+"] name["+name+"] lastrow["+Integer.toString(lastrow)+"]");
	}

	/**
	 * Put
	 * 
	 * @param nsname String
	 * @param name String
	 * @return 
	 * @throws SimpleObjectStorageException
	 */
	public SimpleObjectPrototype get(String nsname, String name) throws SimpleObjectStorageException {
		Logger.logInfo(this.getClass()+".get, storage:["+storageName+"]"+".get: nsname["+nsname+"] name["+name+"]");
	
		if ( (null == nsname) || ( nsname.length() == 0 ) )
			throw new SimpleObjectStorageException( this.getClass()+".get storage:["+storageName+"]"+": nsname cannot be null/empty" );

		if ( (null == name) || ( name.length() == 0 ) )
			throw new SimpleObjectStorageException( this.getClass()+".get storage:["+storageName+"]"+": name cannot be null/empty" );

	
		ResultSet result = null;
		
		boolean found = false;

		SimpleObjectPrototype proto = new SimpleObjectPrototype();
		
		try {
			result = this.db.execute(	"SELECT class, content,meta,expires,modified,created FROM simpleobjectstore WHERE nsname=? AND name=?", 
										new String[] {nsname, name} );
			
			//Logger.logDir(result);
			
			// we should only get one element (if any)
			// and that's what we assume
			if (result.isValidRow()) {
				
				proto.put("classe",   new String( result.getFieldAsString(0)) );
				proto.put("content",  new String( result.getFieldAsString(1)) );
				proto.put("meta",     new String( result.getFieldAsString(2)) );
				proto.put("expires",  new String( result.getFieldAsString(3)) );
				proto.put("modified", new String( result.getFieldAsString(4)) );
				proto.put("created",  new String( result.getFieldAsString(5)) );
				
				found = true;
				
			} else {
				Logger.logWarn(this.getClass()+".get, storage:["+storageName+"]"+": row is not valid");
			}

			result.close();
			
		} catch(DatabaseException e) {
			//Logger.logError(this.getClass()+".get storage:["+storageName+"]"+": DatabaseException raised whilst SELECTing. Msg= " +e.getMessage());
			throw new SimpleObjectStorageException( e.getMessage() );
		} catch( Exception e ) {
			//Logger.logError(this.getClass()+".get, storage:["+storageName+"]"+": Exception raised whilst SELECTing. Msg= " +e.getMessage());
			throw new SimpleObjectStorageException( e.getMessage() );
		}

		if (!found) {
			Logger.logWarn(this.getClass()+".get, storage:["+storageName+"]"+".get: nsname["+nsname+"] name["+name+"] not found");
			return null;
		}
				
		//Logger.log(thisClass+" storage:["+storageName+"]"+".get: got name["+name+"]");
		Logger.logDebug(this.getClass()+".get, storage:["+storageName+"]"+".get: nsname["+nsname+"] name["+name+"] FOUND");
		return proto;
	}
	/**
	 * Delete a document
	 * @param
	 */
	public void delete(String nsname, String name) throws SimpleObjectStorageException {
		Logger.logInfo(this.getClass()+".delete, storage:["+storageName+"]"+": nsname["+nsname+"] name["+name+"]");
		
		if ( (null == nsname) || ( nsname.length() == 0 ) )
			throw new SimpleObjectStorageException( this.getClass()+".delete storage:["+storageName+"]"+": nsname cannot be null/empty" );

		if ( (null == name) || ( name.length() == 0 ) )
			throw new SimpleObjectStorageException( this.getClass()+".delete storage:["+storageName+"]"+": name cannot be null/empty" );
		
		try {
			this.db.execute(	"DELETE FROM simpleobjectstore WHERE nsname=? AND name=?", new String[] {nsname,name} );
		} catch(DatabaseException e) {
			throw new SimpleObjectStorageException( e.getMessage() );
		} 
	}
	/**
	 * Clears an entire namespace
	 * 
	 * @param nsname String
	 * @throws DocumentStoreException
	 */
	public void clearNamespace(String nsname) throws SimpleObjectStorageException {
		
		if ( (null == nsname) || ( nsname.length() == 0 ) )
			throw new SimpleObjectStorageException( this.getClass()+".clearNamespace storage:["+storageName+"]"+": nsname cannot be null/empty" );

		Logger.logInfo(this.getClass()+".clearNamespace, storage:["+storageName+"]");		
		
		try {
			this.db.execute(	"DELETE FROM simpleobjectstore WHERE nsname=?", new String[] {nsname} );
		} catch(DatabaseException e) {
			throw new SimpleObjectStorageException( e.getMessage() );
		}
	}
	
	/**
	 * Clears the entire table
	 */
	public void clear() throws SimpleObjectStorageException {
		Logger.logWarn( this.getClass()+".clear" );
		
		try {
			this.db.execute("DELETE FROM simpleobjectstore" );
		} catch(DatabaseException e) {
			throw new SimpleObjectStorageException( e.getMessage() );
		}
	}
	
	public boolean exists() {
		boolean r = isGearsInstalled();
		return r;
	}

	public boolean isPersistent() {
		return true;
	}

	public boolean initialize() throws SimpleObjectStorageException {
		
		if (!isGearsInstalled())
			return false;
		
		Logger.logInfo( this.getClass()+".initialize" );
		
		if (this.db != null)
			return false;
		
		if (this.storageName.length() == 0) {
			throw new SimpleObjectStorageException("database name cannot be null");
		}
		
		createDatabase();
		
		return true;
	}
	
	/*===================================================================
	 * PROTECTED 
	 ===================================================================*/
	
	protected void createDatabase() throws SimpleObjectStorageException {
		Logger.logDebug( this.getClass()+".createDatabase" );
		
		Factory factory = Factory.getInstance();
		this.db = factory.createDatabase();
		this.db.open( this.storageName );
		try {
			
			this.db.execute("create table if not exists simpleobjectstore "+thisSchema);
			
		} catch(DatabaseException e) {
			Logger.logError(this.getClass()+".createDatabase, storage:["+storageName+"]"+":exception raised whilst creating table. Msg= " + e.getMessage());
			try {
				Logger.logError(this.getClass()+".createDatabase, storage:["+storageName+"]"+":dropping table. Msg= " + e.getMessage());
				this.db.execute("drop table simpleobjectstore");
			} catch(Exception e2) {
				Logger.logError(this.getClass()+".createDatabase, storage:["+storageName+"]"+":dropping table raised exception. Msg= " + e2.getMessage());
			}
			throw new SimpleObjectStorageException( e.getMessage() );
		}
		Logger.logDebug(this.getClass()+".createDatabase, storage:["+storageName+"]"+":created database name["+this.storageName+"]");		
	}
	
	/*===================================================================
	 * PRIVATE 
	 ===================================================================*/
	public static native boolean isGearsInstalled() /*-{
		try {
			return $wnd.google.gears.factory != null;
		} catch (e) {
			return false;
		}
	}-*/;

}
